package com.radar.example;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.radar.algorithm.MathCommon;
import com.radar.common.*;
import com.radar.exception.RadarCaclError;
import com.radar.exception.RadarDecodeError;
import com.radar.io.CinradRadar;
import org.meteoinfo.global.Extent;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.Variable;

import java.awt.geom.Ellipse2D;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class CappiExample {

    static Log log = LogFactory.get();

    public static void main(String[] args) {
//        List<File> files = getFiles();
        String outDir = "/Users/xx/data/radarout/";
        Date date = DateUtil.parse("202105130630", DateConstants.DATE_FORMAT_YYYYMMDDHHMM);
        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
//        date = DateUtil.offsetMinute(date,6);
//        process(date, outDir);
    }

    public static void process(Date date, String outputDir) {
        long time = System.currentTimeMillis();
        FileUtil.mkdir(outputDir);
        double[] alt = new double[]{500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000};
        Extent extent = new Extent(85, 115, 18, 38);
        double[] yarray = new double[(int) ((extent.maxY - extent.minY) / 0.01 + 1)];
        double[] xarray = new double[(int) ((extent.maxX - extent.minX) / 0.01 + 1)];
        IntStream.range(0, yarray.length).forEach(i -> yarray[i] = extent.minY + i * 0.01);
        IntStream.range(0, xarray.length).forEach(i -> xarray[i] = extent.minX + i * 0.01);
        byte[][][] data = new byte[alt.length][yarray.length][xarray.length];
        MathCommon.fill(data);
        List<File> files = getFiles(DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM));
        log.info("process radar date : " + DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI) + " mosaic station num : " + files.size() + "  output Dir : " + outputDir);
        List<String> stations = new ArrayList<>();
        for (File file : files) {
            try {
                CinradRadar cinradRadar = new CinradRadar();
                cinradRadar.parse(file.getAbsolutePath());
                Extent subExtent = getExtent(ProductType.REF, cinradRadar, 0);
                String station = cinradRadar.radar.getRadarSite().getStation();
                if (!extent.include(subExtent)) {
                    log.error("extent is not include subExtent station : " + station);
                    continue;
                }
                stations.add(station);
                double[] sub_yarray = new double[(int) ((subExtent.maxY - subExtent.minY) / 0.01 + 1)];
                double[] sub_xarray = new double[(int) ((subExtent.maxX - subExtent.minX) / 0.01 + 1)];
                IntStream.range(0, sub_yarray.length).forEach(i -> sub_yarray[i] = subExtent.minY + i * 0.01);
                IntStream.range(0, sub_xarray.length).forEach(i -> sub_xarray[i] = subExtent.minX + i * 0.01);
                byte[][][] cappi = cinradRadar.radar.cappiTest(ProductType.REF, alt, sub_xarray, sub_yarray);
                // add all data
                int xindex = (int) ((subExtent.minX - extent.minX) / 0.01);
                int yindex = (int) ((subExtent.minY - extent.minY) / 0.01);
                for (int i = 0; i < cappi.length; i++) {
                    byte[][] rd = getMaskDat(cappi[i]);
                    for (int y = yindex; y < yindex + rd.length; y++) {
                        for (int x = xindex; x < xindex + rd[0].length; x++) {
                            // 新数组为有效值 continue
                            if (rd[y - yindex][x - xindex] == (byte) -128) {
                                continue;
                            }
                            // 原数组为无效值时 直接copy
                            if (data[i][y][x] == (byte) -128) {
                                data[i][y][x] = rd[y - yindex][x - xindex];
                                continue;
                            }
                            // 原数组为 range value continue
                            if (data[i][y][x] == (byte) -100 && rd[y - yindex][x - xindex] == (byte) -100) {
                                continue;
                            }
                            // 原数组为 range value 新数组为有效值
                            if (data[i][y][x] == (byte) -100 && rd[y - yindex][x - xindex] != (byte) -100) {
                                data[i][y][x] = rd[y - yindex][x - xindex];
                                continue;
                            }
                            // 原数组为 有效值 新数组也为有效值
                            if (data[i][y][x] != (byte) -100
                                    && rd[y - yindex][x - xindex] != (byte) -100) {
                                // 判断大小
                                if (MathCommon.DecodeRef(rd[y - yindex][x - xindex]) > MathCommon.DecodeRef(data[i][y][x])) {
                                    data[i][y][x] = rd[y - yindex][x - xindex];
                                }
                                continue;
                            }
                        }
                    }
                }
            } catch (Exception radarDecodeError) {
                log.error("parser radar is error file : " + file);
                continue;
            }

        }
        // write images
        try {
            writeNC(outputDir + File.separator + DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM) + ".nc",
                    stations, xarray, yarray, alt, date, data);
        } catch (Exception e) {
            log.error("write nc error : " + e);
        }
        System.out.println(System.currentTimeMillis() - time);
    }

    public static byte[][] getMaskDat(byte[][] rdata) {
        byte[][] mask;
        Ellipse2D shape = new Ellipse2D.Float();
        shape.setFrame(0, 0, rdata[0].length, rdata.length);
        mask = new byte[rdata.length][rdata[0].length];
        for (int i = 0; i < mask.length; i++) {
            for (int j = 0; j < mask[0].length; j++) {
                if (shape.contains(j, i)) {
                    mask[i][j] = rdata[i][j] == (byte) -128 ? (byte) -100 : rdata[i][j];
                } else {
                    mask[i][j] = (byte) -128;
                }
            }
        }
        return mask;
    }

    public static void writeNC(String ncFile, List<String> stations, double[] xarray, double[] yarray, double[] altArray, Date date, byte[][][] data) throws Exception {
        NetcdfFileWriter write = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf4, ncFile);
        NetcdfFile netcdfFile = null;
        if (write.isDefineMode()) {
            write.create();
            netcdfFile = write.getNetcdfFile();
        }

        // add global attriubte
        List<Attribute> globalAttr = new ArrayList<>();
        globalAttr.add(new Attribute("title", "MOSAIC RADAR CAPPI STRUCT"));

        globalAttr.add(new Attribute("fileName", new File(ncFile).getName()));
        Attribute references = new Attribute("references", "https://github.com/CyanideCN/PyCINRAD");
        globalAttr.add(references);
        globalAttr.add(new Attribute("description", ""));
        String station = "";
        for (String s : stations) {
            station += s + ",";
        }
        station = station.substring(0, station.length() - 1);
        globalAttr.add(new Attribute("mosaic_stations", station));
        globalAttr.add(new Attribute("scan_time", DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI)));
        for (Attribute attribute : globalAttr) {
            netcdfFile.addAttribute(netcdfFile.getRootGroup(), attribute);
        }
        write.setRedefineMode(true);
        // add lon lat alt Dimension
        write.addDimension(null, "Longitude", xarray.length);
        write.addDimension(null, "Latitude", yarray.length);
        write.addDimension(null, "Altitude", altArray.length);

        Variable v1 = write.addVariable(null, "Longitude", DataType.DOUBLE, "Longitude");
        v1.addAttribute(new Attribute("units", "degrees_east"));
        v1.addAttribute(new Attribute("actual_range", Arrays.asList(xarray[0], xarray[xarray.length - 1])));

        Variable v2 = write.addVariable(null, "Latitude", DataType.DOUBLE, "Latitude");
        v2.addAttribute(new Attribute("units", "degrees_north"));
        v2.addAttribute(new Attribute("actual_range", Arrays.asList(yarray[0], yarray[yarray.length - 1])));

        Variable v3 = write.addVariable(null, "Altitude", DataType.DOUBLE, "Altitude");
        v3.addAttribute(new Attribute("units", "m"));
        v3.addAttribute(new Attribute("actual_range", Arrays.asList(altArray[0], altArray[altArray.length - 1])));

        Variable cappi = write.addVariable(null, "CAPPI", DataType.BYTE, Arrays.asList(
                netcdfFile.findDimension("Altitude"),
                netcdfFile.findDimension("Latitude"),
                netcdfFile.findDimension("Longitude")));
        cappi.addAttribute(new Attribute("missing_value", (byte) -128));
        cappi.addAttribute(new Attribute("range_value", (byte) -100));
        cappi.addAttribute(new Attribute("add_offset", 32.5f));
        cappi.addAttribute(new Attribute("scale_factor", 0.5f));
        cappi.addAttribute(new Attribute("full_name", ProductType.REF.getFullName()));
        cappi.addAttribute(new Attribute("units", ProductType.REF.getUnit()));

        write.setRedefineMode(false);

        write.write(v1, MathCommon.toArray(xarray));
        write.write(v2, MathCommon.toArray(yarray));
        write.write(v3, MathCommon.toArray(altArray));

        // write byte data
        write.write(cappi, MathCommon.toByteArray(data));
        netcdfFile.close();
        write.abort();
        write.close();
    }

    public static List<File> getFiles(String date) {
        String file = "/Users/xx/data/radar";
        Date nd = DateUtil.parse(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM);
        List<File> files = FileUtil.loopFiles(file).stream().filter(i -> Pattern.matches("^Z_RADR_I_Z(\\d{4})_(\\d{14})_O_DOR_(.*?)_CAP_FMT.bin.bz2$", i.getName())).collect(Collectors.toList());
        Map<String, List<File>> collect = files.stream().collect(Collectors.groupingBy(i -> i.getName().split("_")[3]));
        List<File> result = new ArrayList<>();
        for (String key : collect.keySet()) {
            List<File> rfiles = collect.get(key);
            long time = Long.MAX_VALUE;
            File f = null;
            for (int i = 0; i < rfiles.size(); i++) {
                String d = rfiles.get(i).getName().split("_")[4];
                long t = Math.abs(DateUtil.parse(d, DateConstants.DATE_TIME_FORMAT_YYYYMMDDHHMISS).getTime() - nd.getTime());
                if (t < time) {
                    time = t;
                    f = rfiles.get(i);
                }
            }
            if (time < 1000 * 60 * 6) {
                result.add(f);
            }
        }
        return result;
    }

    public static Extent getExtent(ProductType type, CinradRadar cinradRadar, int index) throws RadarCaclError, RadarDecodeError, IOException, InvalidRangeException {
        Extent extent = null;
        double lon = cinradRadar.radar.getRadarSite().getLon();
        double lat = cinradRadar.radar.getRadarSite().getLat();
        if (type == ProductType.REF || type == ProductType.VEL) {
            double res = cinradRadar.radar.getResolution(type);
            byte[][] raw = cinradRadar.radar.getRaw(index, type);
            double ele = cinradRadar.radar.getElevation()[index];
            double distance = raw[0].length * res;
            distance = Math.cos(ele * Const.DEG2RAD) * distance;
            extent = Common.getExtentRadius(lon, lat, distance);
        } else if (type == ProductType.CR) {
            extent = cinradRadar.radar.getCR().getExtent();
        } else if (type == ProductType.ET) {
            extent = cinradRadar.radar.getET().getExtent();
        } else if (type == ProductType.VIL) {
            extent = cinradRadar.radar.getVIL().getExtent();
        }
        return extent;
    }
}
